{
  const person = { name: 'yideng' };

  function sayHi(age) {
    return `${this.name} is ${age}`;
  }

  console.log(sayHi.call(person, 5)); // .call方法会立即执行 所以结果是 yideng is 5
  console.log(sayHi.bind(person, 5)); // .bind方法返回函数值得拷贝，含带有绑定得上下文，它不会立即执行 所以结果是 函数sayHi
}

{
  const num = {
    a: 10,
    add() {
      return this.a + 2;
    },
    reduce: () => this.a - 2,
  };
  console.log(num.add()); // 这里this指向num，所以是12
  console.log(num.reduce()); // 箭头函数绑定它爹的this，在这里this指向window是undefined，所以结果是NaN
}

{
  var foo = {
    bar: function () {
      return this.baz;
    },
    baz: 1,
  };
  console.log(typeof (f = foo.bar)()); // undefined
  // 等价于下面执行流程
  var f = foo.bar; // 这里this执行window
  console.log(typeof f());
}

{
  function f() {
    return f;
  }
  console.log(new f() instanceof f); // false
  // 解析
  // a instanceof b用于检测a是不是b的实例，如果题目中没有return f，则答案是true；
  // 而在本题中new f()返回的结果是函数f的函数对象，并不是f的实例，所以是flase

  function f() {}
  console.log(new f() instanceof f); // true
}

{
  var x = 1;
  if (function f() {}) {
    x += typeof f;
  }
  console.log(x); // 1undefined
  // 解析
  // 函数声明写在运算符中，其为true，但放在运算符中的函数声明在执行阶段是找不到的；
  // 所以结果是 1 + 'undefined'
}

{
  var foo = function bar() {
    return 12;
  };
  console.log(typeof bar()); // 报错 bar is not defined
  // 解析
  // 这种命名函数表达式函数只能在函数体内有效，外部是访问不道的
}

{
  var company = {
    address: 'beijing',
  };
  var yideng = Object.create(company);
  delete yideng.address;
  console.log(yideng.address); // beijing
  // 解析
  // 这里的yideng是用过prototype继承了company的address，yideng上面并没有address属性，所以delete是无效的
}

{
  var fullname = 'a';
  var obj = {
    fullname: 'b',
    prop: {
      fullname: 'c',
      getFullname: function () {
        return this.fullname;
      },
    },
  };

  console.log(obj.prop.getFullname()); // c this指向props
  var test = obj.prop.getFullname;
  console.log(test()); // a this指向window
}

{
  (function () {
    var a = (b = 5);
  })();

  console.log(b); // 5
  console.log(a); // 报错 a is not defined
}

{
  var a = 1;
  (function a() {
    a = 2;
    console.log(a); // 函数体本身a
  })();
  // 解析
  // 立即调用的函数表达式(IIFE)，有一个独立的作用域，如果函数名称和内部变量冲突，就会永远执行函数本身。所以上面输出的是函数本身
}

{
  function side(arr) {
    arr[0] = arr[2];
  }
  function a(a, b, c = 3) {
    c = 10;
    side(arguments);
    return a + b + c;
  }
  console.log(a(1, 1, 1)); // 12
  // 解析
  // arguments中的c是1，不是10
  // 因为a函数加了默认值，就会按照es6的方式解析，es6是有块级作用域的，所以c是永远不会改变的
  // 注意如果函数a没有默认值，这个时候c是会改变的，结果是21
}

{
  console.log(1);
  setTimeout(() => {
    console.log(2);
    process.nextTick(() => {
      console.log(3);
    });
    new Promise((resolve) => {
      console.log(4);
      resolve();
    }).then(() => {
      console.log(5);
    });
  });

  new Promise((resolve) => {
    console.log(7);
    resolve();
  }).then(() => {
    console.log(8);
  });

  process.nextTick(() => {
    console.log(6);
  });

  setTimeout(() => {
    console.log(9);
    process.nextTick(() => {
      console.log(10);
    });
    new Promise((resolve) => {
      console.log(11);
      resolve();
    }).then(() => {
      console.log(12);
    });
  });
  // node<11
  // 1 7 6 8 2 4 9 11 3 10 5 12
  // node>=11:
  // 1 7 6 8 2 4 3 5 9 11 10 12
  // 解析（注意nextTick是优先于其他微任务执行得，介于同步和micro task之间）
  // 宏任务和微任务
  // 宏任务：macrotask, 包括setTimeout、setInerVal、setImmediate(node独有) 、requestAnimationFrame(浏览器独有) 、I/O、UI rendering(浏览器独有)
  // 微任务：microtask, 包括process.nextTick(Node独有) 、Promise.then() 、Object.observe、MutationObserver
  // NodeJS中微队列主要有2个
  // 1.Next Tick Queue：是放置process.nextTick(callback)的回调任务的
  // 2.Other Micro Queue：放置其他microtask，比如Promise等
  // 在浏览器中，也可以认为只有一个微队列，所有的microtask都会被加到这一个微队列中，但是在NodeJS中，不同的microtask会被放置在不同的微队列中。
  // Node.js中的EventLoop过程
  // 1.执行全局Script的同步代码
  // 2.执行microtask微任务，先执行所有Next Tick Queue中的所有任务，再执行Other Microtask Queue中的所有任务
  // 3.开始执行macrotask宏任务，共6个阶段，从第1个阶段开始执行相应每一个阶段macrotask中的所有任务，注意，这里是所有每个阶段宏任务队列的所有任务，在浏览器的Event Loop中是只取宏队列的第一个任务出来执行，每一个阶段的macrotask任务执行完毕后，开始执行微任务，也就是步骤2
  // 4.Timers Queue -> 步骤2 -> I / O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue ......
  // 5.这就是Node的Event Loop
  // Node 11.x新变化
  // 现在node11在timer阶段的setTimeout, setInterval...和在check阶段的immediate都在node11里面都修改为一旦执行一个阶段里的一个任务就立刻执行微任务队列。为了和浏览器更加趋同.
}

{
  // 写一个 mySetInterVal(fn, a, b),a,a+b,a+2b,...,a+nb 的时间，然后写一个 myClear，停止上面的 mySetInterVal
  function mySetInterVal(fn, a, b) {
    this.a = a;
    this.b = b;
    this.time = 0;
    this.timerId = null;

    this.run = () => {
      this.timerId = setTimeout(() => {
        fn();
        this.time++;
      }, this.a + this.time * this.b);
    };

    this.clear = () => {
      clearTimeout(this.timerId);
      this.time = 0;
    };
  }

  var a = new mySetInterVal(
    () => {
      console.log('123');
    },
    1000,
    2000
  );
  a.run();
  a.clear();
}

{
  // 在一个异步promise函数中，我有一段代码，那么我想暂停，等待其它事件比如外面一个用户点击行为，在接着恢复继续执行后面代码，怎么实现？
  // 思路：把resolve复制给全局变量，然后在条件里面调用这个全局变量改变promise状态
  let r = null;
  new Promise((res) => {
    r = res;
  }).then((res) => {
    console.log(res);
  });

  setTimeout(() => {
    r(1);
  }, 3000);
}

{
  // 有一个类，在new实例化的时候有异步代码逻辑，我想在调用get方法的时候，必须在初始化完全完成后（异步逻辑执行完毕），在去执行get方法
  class Demo {
    vaule = {}; // 存放结果字典
    stack = []; // 存放回调队列
    isReady = false; // 锁
    constructor() {
      console.log('异步调用中...');
      // 有异步方法处理
      setTimeout(() => {
        console.log('异步调用完成');
        this.vaule.kk1 = 'demo';
        this.vaule.kk2 = 'demo2';
        this.isReady = true;
        this.run();
      }, 1000);
    }
    get({ key, cb }) {
      this.stack.push({
        key,
        cb,
      });

      if (this.isReady) {
        this.run();
      }
    }

    // 开始执行
    run() {
      while (this.stack.length > 0) {
        const item = this.stack.shift();
        item.cb(this.vaule[item.key]);
      }
    }
  }

  const demo = new Demo();
  demo.get({
    key: 'kk1',
    cb: (value) => {
      console.log('get1', value);
    },
  });
  demo.get({
    key: 'kk2',
    cb: (value) => {
      console.log('get2', value);
    },
  });
  setTimeout(() => {
    demo.get({
      key: 'kk1',
      cb: (value) => {
        console.log('get1> 延迟', value);
      },
    });
  }, 3000);
}

{
  // 提供一个类上面有put方法和get方法，只有当put修改过数据的时候，get才可以返回数据，
  // 如果put没有修改指定key时，调用get方法（指定key时），get这个方法不会被调用（暂停阶段，等待put触发），
  // 当后续操作有调用put（相应的key）后，会恢复之前get暂停的那些方法，依次调用get
  class Demo {
    data = {
      a: 1,
      b: 2,
      c: 3,
    };
    stack = [];
    historyKey = {};
    // 修改数据
    put(key, value) {
      this.data[key] = value;
      this.historyKey[key] = true;
      this.#runStack();
    }
    // 获取数据
    get(key, cb) {
      // 有操作记录
      if (this.historyKey[key]) {
        cb(this.data[key]);
        return;
      }
      this.stack.push({
        key,
        cb,
      });
    }
    #runStack() {
      while (this.stack.length > 0) {
        const item = this.stack.shift();
        item.cb(this.data[item.key]);
      }
    }
  }

  const demo = new Demo();

  demo.get('a', (v) => {
    console.log('获取a', v);
  });
  demo.put('a', 11);
  demo.get('a', (v) => {
    console.log('put修改后获取a', v);
  });
  demo.get('b', (v) => {
    console.log('b1>', v);
  });
  demo.get('b', (v) => {
    console.log('b2>', v);
  });

  demo.put('b', 22);
}

{
  class Demo {
    // class 内部默认使用严格模式
    a = 2;
    log() {
      console.log('get', this);
    }
    logA = () => {
      console.log('getA', this);
    };
  }

  const { log, logA } = new Demo();
  log(); // undefined 严格模式下全局是undefined，此时this指向全局，所以是undefined
  logA(); // 箭头函数绑定父级this，也就是类本身

  // const demo = new Demo();
  // demo.log();
  // demo.log();

  function demo2() {
    'use strict';
    console.log(this); // 严格模式下全局是undefined
  }
  demo2();
}

{
  function Foo() {
    console.log(this.a);
    this.a = 1;
  }
  Foo.prototype.show = function () {
    console.log(this.a);
  };
  Foo(); // undefined this指向window
  var obj1 = new Foo();
  obj1.show(); // 1 this指向实例，也就是类本身

  var bar = Foo.bind({ a: 2 });
  bar(); // 2 this指向绑定的对象
  var obj2 = new bar(); // bind返回的函数被new，bind不起作用，this指向new出来的实例
  obj2.show(); // 1 bind内部维护了原型关系的继承
}
